#include "pager.h"

#ifdef TEST_PAGE
#define PRIVATE
#else
#define PRIVATE static
#endif

/* PRIVATE function declaration start */

PRIVATE int allocPages(pager_t *pager, uint16_t pgnums);
PRIVATE void freePages(pager_t *pager);
PRIVATE void initPage(page_t *page, int type);
PRIVATE int isEnoughMem(page_t *page);
PRIVATE void cleanFreeMem(page_t *page);

/* PRIVATE function declaration end */

int flushPageToDisk(page_t *page, FILE *file)
{
    if (fwrite(page->mem, sizeof(char), PAGE_SIZE, file) != PAGE_SIZE)
        return -1;
    return 0;
}

int loadPageFromDisk(page_t *page, FILE *file)
{
    if (fread(page->mem, sizeof(char), PAGE_SIZE, file) != PAGE_SIZE) {
        cleanPage(page);
        return -1;
    }

    /* update the lower and upper */
    int count = 0;
    while ((page->slot + count)->len != 0) {
        page->lower += sizeof(slot_t);
        page->upper -= (page->slot + count)->len;
        count++;
    }

    // page->lower = (char *) (page->slot + count);
    // page->upper = (char *) (page->end - (page->slot + count - 1)->off);

    return 0;
}

pager_t *createPager(int type)
{
    pager_t *pager = (pager_t *) malloc(sizeof(pager_t));
    if (pager == NULL) {
        fprintf(stderr, "create pager error\n");
        return NULL;
    }
    pager->pgnums = 0;
    pager->pgpool = NULL;
    pager->type = type;

    if (allocPages(pager, ALLOC_PAGES) < 0) {
        destroyPager(pager);
        return NULL;
    }

    return pager;
}

void destroyPager(pager_t *pager)
{
    freePages(pager);
    free(pager);
}

int getPage(pager_t *pager, page_t *page)
{
    uint8_t *p_is_used = pager->pgpool;
    int is_found = 0;

    for (int i = 0; i < pager->pgnums; i++) {
        if (*p_is_used == (uint8_t) UNUSED) {
            is_found = 1;
            break;
        }
        else 
            p_is_used = (uint8_t *) ((char *) p_is_used + PAGE_SIZE);
    }

    if (is_found == 0) {
        if (allocPages(pager, ALLOC_PAGES) < 0)
            return -1;
    }

    page->mem = (void *) p_is_used;
    initPage(page, pager->type);

    return 0;
}

void cleanPage(page_t *page)
{
    if (page == NULL)
        return;

    if (page->mem != NULL)
        PAGE_SET(page->is_used, UNUSED);

    page->mem       = NULL; 
    page->is_used   = NULL; 
    page->type      = NULL; 
    page->slot      = NULL; 
    page->end       = NULL; 
    page->lower     = NULL; 
    page->upper     = NULL; 
}

/*
 * insert a record to leaf page
 * -1 for error, 0 for success, 1 for duplicate key
 */
int insertRecordToPage(page_t *page, record_t *record)
{
    if (page == NULL || record == NULL)
        return -1;

    if (isEnoughMem(page) < 0) {
        fprintf(stderr, "Out of memory\n");
        return -1;
    }

    /* find the insert position */
    int insert_pos = -1;
    for (int i = 0; i < PAGE_RECORD_NUM(page); i++) {
        if (record->index < (page->slot + i)->index) {
            insert_pos = i;
            break;
        } else if (record->index == (page->slot+i)->index) {
            fprintf(stderr, "duplicate key: %d\n", record->index);
            return 1;
        } else 
            continue;
    }
#define ORDER
#ifdef ORDER
    if (insert_pos != -1) {     /* need to move slots and records */
        /* left space for inserting slot and record */
        slot_t *insert_slot = (slot_t *) (page->slot + insert_pos);
        char *replaced_record_pos = (char *) page->end - insert_slot->off;
        char *insert_record_pos = replaced_record_pos + insert_slot->len - RECORD_LEN(record);
        memcpy(page->upper - RECORD_LEN(record), page->upper, 
            replaced_record_pos + insert_slot->len - (char *) page->upper);
        memcpy(insert_slot + 1, insert_slot, (char *) page->lower - (char *) insert_slot);

        /* update the insert slot's info */
        insert_slot->slot_id = insert_slot - page->slot;
        insert_slot->index = record->index;
        insert_slot->off = (char *) page->end - insert_record_pos;
        insert_slot->len = RECORD_LEN(record);

        /* update the page's lower and upper pointer */
        page->lower += sizeof(slot_t);
        page->upper -= RECORD_LEN(record);

        /* update the moved slots' info */
        for (int i = insert_pos + 1; i < PAGE_RECORD_NUM(page); i++) {
            (page->slot + i)->slot_id = i;
            (page->slot + i)->off += RECORD_LEN(record);
        }

        if (serialize(record, insert_record_pos) < 0) {
            fprintf(stderr, "serialize error\n");
            return -1;
        }
    } else {    /* append to the page */
#endif  /* ORDER */
        slot_t *slot = (slot_t *) page->lower;

        if (serialize(record, page->upper - RECORD_LEN(record)) < 0) {
            fprintf(stderr, "serialize error\n");
            return -1;
        }

        page->lower += sizeof(slot_t);
        page->upper -= RECORD_LEN(record);

        slot->slot_id = slot - page->slot;
        slot->index = record->index;
        slot->off = (char *) page->end - page->upper;
        slot->len = RECORD_LEN(record); 
#ifdef ORDER
    }
#endif  /* ORDER */

    cleanFreeMem(page);     /* clean the unused memory (set it to 0) */

    return 0;
}

/*
 * delete a record according to its index from a leaf page
 * 0 for success, 1 for non-exist record
 */
int deleteRecordfromPage(page_t *page, uint16_t index)
{
    if (page == NULL)
        return -1;

    /* find the insert position */
    int delete_pos = -1;
    for (int i = 0; i < PAGE_RECORD_NUM(page); i++) {
        if (index == (page->slot + i)->index) {
            delete_pos = i;
            break;
        }
    }

    if (delete_pos == -1) {     /* not found */
        return 1;
    } else {                    /* found */
        /* delete the record and slot */
        slot_t *delete_slot = (slot_t *) (page->slot + delete_pos);
        char *delete_record_pos = (char *) page->end - delete_slot->off;
        int delete_record_len = delete_slot->len;
        memcpy(page->upper + delete_record_len, page->upper,
            delete_record_pos - (char *) page->upper);
        memcpy(delete_slot, delete_slot + 1, (char *) page->lower - (char *) (delete_slot + 1));

        /* update the page's lower and upper pointer */
        page->lower -= sizeof(slot_t);
        page->upper += delete_record_len;

        /* update the moved slot info */
        for (int i = delete_pos; i < PAGE_RECORD_NUM(page); i++) {
            (page->slot + i)->slot_id = i;
            (page->slot + i)->off -= delete_record_len;
        }

        cleanFreeMem(page);     /* clean the unused memory (set it to 0) */
        return 0;
    }
}

char *getRecordFromPage(page_t *page, uint16_t index)
{
    if (page == NULL)
        return NULL;

    slot_t *slot = NULL;

    for (int i = 0; i < PAGE_RECORD_NUM(page); i++) {
        if ((page->slot + i)->index == index) {
            slot = page->slot + i;
            break;
        }
    }

    if (slot == NULL)
        return NULL;

    char *start = (char *) page->end - slot->off;

    return start;
}

/*
 * dest is the pointer to the storation in page
 * src is the pointer to the structure record_t 
 */
int serialize(record_t *src, char *dest)
{
    if (src == NULL || dest == NULL)
        return -1;

    size_t offset = 0;     /* memcpy offset */
    size_t len = 0;        /* memcpy length */

    switch (src->table) {
        case BORROW: /* fixed length */
            /* uint16_t borrow_id */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_borrow.borrow_id), len);
            offset += len;

            /* uint16_t book_id */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_borrow.book_id), len);
            offset += len;

            /* uint16_t user_id   */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_borrow.user_id), len);
            offset += len;

            /* uint16_t borrow_date   */
            len = sizeof(date_t);
            memcpy(dest + offset, &(src->val_borrow.borrow_date), len);
            offset += len;

            /* uint16_t return_date   */
            len = sizeof(date_t);
            memcpy(dest + offset, &(src->val_borrow.return_date), len);
            offset += len;

            /* uint8_t status   */
            len = sizeof(uint8_t);
            memcpy(dest + offset, &(src->val_borrow.status), len);
            offset += len;
            break;

        case USER: /* variable length */
            /* uint16_t user_id */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_user.user_id), len);
            offset += len;

            /* char name[MAX_NAME_LEN] */
            len = strlen(src->val_user.name) + 1;   /* null terminator */
            memcpy(dest + offset, src->val_user.name, len);
            offset += len;

            /* char email[MAX_NAME_LEN] */
            len = strlen(src->val_user.email) + 1;   /* null terminator */
            memcpy(dest + offset, src->val_user.email, len);
            offset += len;
            break;

        case BOOK: /* variable length */
            /* uint16_t book_id   */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_book.book_id), len);
            offset += len;

            /* uint16_t quantity  */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_book.quantity), len);
            offset += len;

            /* uint16_t available */
            len = sizeof(uint16_t);
            memcpy(dest + offset, &(src->val_book.available), len);
            offset += len;

            /* uint8_t  category  */
            len = sizeof(uint8_t);
            memcpy(dest + offset, &(src->val_book.category), len);
            offset += len;

            /* char title[MAX_NAME_LEN] */
            len = strlen(src->val_book.title) + 1;   /* null terminator */
            memcpy(dest + offset, src->val_book.title, len);
            offset += len;

            /* char author[MAX_NAME_LEN] */
            len = strlen(src->val_book.author) + 1;   /* null terminator */
            memcpy(dest + offset, src->val_book.author, len);
            offset += len;
            break;

        default:
            return -1;
    }

    return 0;
}

/*
 * src is the pointer to the storation in page
 * dest is the pointer to the structure record_t 
 */
int deserialize(char *src, record_t *dest)
{
    if (src == NULL || dest == NULL)
        return -1;

    size_t offset = 0;     /* memcpy offset */
    size_t len = 0;        /* memcpy length */

    switch (dest->table) {
        case BORROW: /* fixed length */
            /* uint16_t borrow_id */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_borrow.borrow_id), src + offset, len);
            offset += len;

            /* uint16_t book_id */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_borrow.book_id), src + offset, len);
            offset += len;

            /* uint16_t user_id */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_borrow.user_id), src + offset, len);
            offset += len;

            /* date_t borrow_date */
            len = sizeof(date_t); 
            memcpy(&(dest->val_borrow.borrow_date), src + offset, len);
            offset += len;

            /* date_t return_date */
            len = sizeof(date_t); 
            memcpy(&(dest->val_borrow.return_date), src + offset, len);
            offset += len;

            /* uint8_t status */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_borrow.status), src + offset, len);
            offset += len;           
            break;

        case USER: /* variable length */
            /* uint16_t user_id */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_user.user_id), src + offset, len);
            offset += len;

            /* char name[MAX_NAME_LEN] */
            len = strlen(src + offset) + 1;   /* null terminator */
            memcpy(dest->val_user.name, src + offset, len);
            offset += len;

            /* char email[MAX_EMAIL_LEN] */
            len = strlen(src + offset)+ 1;   /* null terminator */
            memcpy(dest->val_user.email, src + offset, len);
            break;

        case BOOK: /* variable length */
            /* uint16_t book_id */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_book.book_id), src + offset, len);
            offset += len;

            /* uint16_t quantity */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_book.quantity), src + offset, len);
            offset += len;

            /* uint16_t available */
            len = sizeof(uint16_t);
            memcpy(&(dest->val_book.available), src + offset, len);
            offset += len;

            /* uint8_t category */
            len = sizeof(uint8_t);
            memcpy(&(dest->val_book.category), src + offset, len);
            offset += len;

            /* char title[MAX_NAME_LEN] */
            len = strlen(src + offset) + 1;   /* null terminator */
            memcpy(dest->val_book.title, src + offset, len);
            offset += len;

            /* char author[MAX_NAME_LEN] */
            len = strlen(src + offset) + 1;   /* null terminator */
            memcpy(dest->val_book.author, src + offset, len);
            offset += len;
            break;

        default:
            return -1;
    }

    return 0;
}

/* PRIVATE function implemention start */

PRIVATE int allocPages(pager_t *pager, uint16_t pgnums)
{
    if (pgnums <= 0)
        return 0;

    if (pager->pgnums == 0) {
        pager->pgpool = malloc(PAGE_SIZE * pgnums);
        if (pager->pgpool == NULL) {
            fprintf(stderr, "page pool alloc error\n");
            return -1;
        }
        memset(pager->pgpool, 0, PAGE_SIZE * pgnums);
        pager->pgnums += pgnums;
    } else {
        pager->pgpool = realloc(pager->pgpool, (pager->pgnums + pgnums) * PAGE_SIZE);
        if (pager->pgpool == NULL) {
            fprintf(stderr, "page pool realloc error\n");
            return -1;
        }
        memset((char *) pager->pgpool + (pager->pgnums) * PAGE_SIZE,
                0, pgnums * PAGE_SIZE);
        pager->pgnums += pgnums;
    }

    return 0;
}

PRIVATE void freePages(pager_t *pager)
{
    /* need to free every page's info (tree) */
    free(pager->pgpool);
}

PRIVATE void initPage(page_t *page, int type)
{
    page->is_used   = (uint8_t *)  PAGE_LOCATE(page->mem, USED_FALG_OFFSET);
    page->type      = (uint8_t *)  PAGE_LOCATE(page->mem, TABLE_TYPE_OFFSET);
    page->slot      = (slot_t *)   PAGE_LOCATE(page->mem, SLOT_OFFSET);
    page->end       = (void *)     PAGE_LOCATE(page->mem, DATA_OFFSET);
    page->lower     = (char *)     page->slot;
    page->upper     = (char *)     page->end;

    PAGE_SET(page->is_used, USED);
    PAGE_SET(page->type, type);
}


PRIVATE int isEnoughMem(page_t *page)
{
    size_t avail_mem = (char *) page->upper - (char *) page->lower;

    if (avail_mem < sizeof(slot_t) + sizeof(record_t))
        return -1;
    else 
        return 0;
}


PRIVATE void cleanFreeMem(page_t *page)
{
    memset(page->lower, 0, (char *) page->upper - (char *) page->lower);
}

/* PRIVATE function implemention start */
